package pku;

import io.Snappy;

import java.io.ByteArrayOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
import java.util.zip.Deflater;

/**
 * 消息的实现
 */
public class DefaultMessage implements ByteMessage {

    private KeyValue headers = new DefaultKeyValue();
    private byte[] body;

    public DefaultMessage() {
        super();
    }

    public DefaultMessage(byte[] body) {
        this.body = body;
    }

    public void setHeaders(KeyValue headers) {
        this.headers = headers;
    }

    public byte[] getBody() {
        return body;
    }

    public void setBody(byte[] body) {
        this.body = body;
    }

    public KeyValue headers() {
        return headers;
    }


    public DefaultMessage putHeaders(String key, int value) {
        headers.put(key, value);
        return this;
    }

    public DefaultMessage putHeaders(String key, long value) {
        headers.put(key, value);
        return this;
    }

    public DefaultMessage putHeaders(String key, double value) {
        headers.put(key, value);
        return this;
    }

    public DefaultMessage putHeaders(String key, String value) {
        headers.put(key, value);
        return this;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(HexDump.toHexString(body)).append(Constants.OBJ_SPLITTER);

        ((DefaultKeyValue) headers).kvs.forEach((key, value) -> {
            sb.append(key).append(Constants.PAIR_SPLITTER).append(value);
            sb.append(Constants.ITEM_SPLITTER);
        });
        sb.append(Constants.OBJ_SPLITTER);
        return sb.toString();
    }

    public byte[] toBytes() {
        byte[] headersBytes = headers.toBytes();
        int msgLen = headersBytes.length + body.length;
        if (msgLen > 1048) {
//            byte[] comData = compress(Utils.byteMergerAll(headersBytes, body));
            byte[] comData = Snappy.compress(Utils.byteMergerAll(headersBytes, body));
            int comLen = comData.length;
            byte[] comLenBytes = Utils.int2bytes3(comLen);
            comLenBytes[2] |= 0x80;
            return Utils.byteMergerAll(comLenBytes, comData);
        } else {
            byte[] msgLenBytes = Utils.int2bytes3(msgLen);
            return Utils.byteMergerAll(msgLenBytes, headersBytes, body);
        }
    }

    public byte[] compress(byte[] indata) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        Deflater compressor = new Deflater(1);
        try {
            compressor.setInput(indata);
            compressor.finish();
            final byte[] buf = new byte[indata.length];
            while (!compressor.finished()) {
                int count = compressor.deflate(buf);
                bos.write(buf, 0, count);
            }
        } finally {
            compressor.end();
        }
        return bos.toByteArray();
    }

    static public DefaultMessage valueOf(byte[] msg) {
        DefaultMessage message = new DefaultMessage();
//        byte headerCnt = msg[0];
        byte[] keyTypeCode = new byte[2];
        keyTypeCode[0] = msg[0];
        keyTypeCode[1] = msg[1];
        List<Byte> keyList = new ArrayList<>();
        int keyCode = (keyTypeCode[1] & 0xff) << 8 | (keyTypeCode[0] & 0xff);
        for (int i = 0; i < 16; i++) {
            int code = 1 << i;
            if((keyCode & code) == code){
                byte b = (byte) (i & 0xff);
                keyList.add(b);
            }
        }
        int curIndex = 2;
        int msgLen = msg.length;
        for (Byte aKeyList : keyList) {
            byte keyType = aKeyList;
            String keyName = Utils.getKeyName(keyType);
            if (keyType < 0x04) {
                byte[] val = new byte[4];
                System.arraycopy(msg, curIndex, val, 0, 4);
                curIndex += 4;
                message.putHeaders(keyName, Utils.bytes2int(val));
            } else if (keyType < 0x08) {
                byte[] val = new byte[8];
                System.arraycopy(msg, curIndex, val, 0, 8);
                curIndex += 8;
                message.putHeaders(keyName, Utils.bytes2long(val));
            } else if (keyType < 0x0a) {
                byte[] val = new byte[8];
                System.arraycopy(msg, curIndex, val, 0, 8);
                curIndex += 8;
                message.putHeaders(keyName, Utils.bytes2double(val));
            } else {
                byte[] strLen = new byte[2];
                strLen[0] = msg[curIndex++];
                strLen[1] = msg[curIndex++];
                int len = Utils.bytes2int2(strLen);
                byte[] strBytes = new byte[len];
                System.arraycopy(msg, curIndex, strBytes, 0, len);
                curIndex += len;
                message.putHeaders(keyName, new String(strBytes));
            }
        }
        byte[] body = new byte[msgLen - curIndex];
        System.arraycopy(msg, curIndex, body, 0, msgLen - curIndex);
        message.setBody(body);
        return message;
    }

    static public DefaultMessage valueOf(String myString) {
        StringBuilder bodyStr = new StringBuilder();
        StringBuilder keyStr = new StringBuilder();
        StringBuilder valueStr = new StringBuilder();

        // 1st: read body
        int currIndex = 0;
        char ch;
        for (; (ch = myString.charAt(currIndex)) != Constants.OBJ_SPLITTER; currIndex++) {
            bodyStr.append(ch);
        }
        DefaultMessage message = new DefaultMessage(HexDump.hexStringToByteArray(bodyStr.toString()));
        currIndex++;

        // 2nd: read headers
        while (myString.charAt(currIndex) != Constants.OBJ_SPLITTER) {
            keyStr.setLength(0);
            for (; (ch = myString.charAt(currIndex)) != Constants.PAIR_SPLITTER; currIndex++) {
                keyStr.append(ch);
            }
            currIndex++;

            valueStr.setLength(0);
            for (; (ch = myString.charAt(currIndex)) != Constants.ITEM_SPLITTER; currIndex++) {
                valueStr.append(ch);
            }
            currIndex++;
            message.putHeaders(keyStr.toString(), valueStr.toString());
        }

        return message;
    }

}
